package service

import (
	"bytes"
	"context"
	"encoding/json"
	"inspector/internal/config"
	store "inspector/internal/dao"
	"inspector/internal/enums"
	"inspector/internal/model/do"
	"inspector/internal/model/entity"
	"inspector/internal/utils"
	"inspector/pkg/log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

type EventSrv interface {
}

type EventService struct {
	store store.Factory
	key   []byte
}

var _ EventSrv = (*EventService)(nil)

func NewEventService(store store.Factory) *EventService {
	key := os.Getenv("ENCRYPTION_KEY")
	if key == "" {
		key = "e137f61e078b6570f4639089b3b24bf9"
	}
	return &EventService{
		store: store,
		key:   []byte(key),
	}
}

// Event max retry count
var maxRetryCount = 3

func (s EventService) ProcessWebhookEvent(ctx context.Context, event do.WebhookEvent) error {

	payload, err := json.Marshal(event)
	if err != nil {
		return err
	}
	tx := s.store.DB()
	ent := &entity.Event{
		EventType:      "Webhook",
		EventId:        event.HookId,
		EventName:      event.EventType,
		CommitId:       event.WebhookPayload.HeadCommit.ID,
		Repository:     event.WebhookPayload.Repository.URL,
		RepositoryId:   strconv.Itoa(event.WebhookPayload.Repository.ID),
		Ref:            event.WebhookPayload.Ref,
		Organization:   event.WebhookPayload.Organization.Login,
		OrganizationId: event.WebhookPayload.Organization.ID,
		InstallationId: event.WebhookPayload.Installation.ID,
		Sender:         event.WebhookPayload.Sender.ID,
		SenderType:     event.WebhookPayload.Sender.Type,
		RequestPayload: string(payload),
		RetryCount:     0,
		EventStatus:    enums.EVENT_PROCESSING,
	}
	ent, err = s.store.Event(tx).Create(ctx, ent)
	err = requestScanServer(event, ent)
	if err != nil {
		return err
	}
	ent.EventStatus = enums.EVENT_SUCCESS
	err = s.store.Event(tx).Update(ctx, ent)
	if err != nil {
		return err
	}
	return nil
}

func requestScanServer(event do.WebhookEvent, ent *entity.Event) error {
	url := config.Get().GetAtomConfig().ScanUrl
	body := do.ScanRequestBody{
		UserId:         strconv.FormatInt(event.WebhookPayload.Installation.ID, 10),
		AppId:          config.Get().GetAtomConfig().AppId,
		AppName:        config.Get().GetAtomConfig().AppName,
		Organization:   event.WebhookPayload.Organization.Login,
		OrganizationID: event.WebhookPayload.Organization.ID,
		Repository:     event.WebhookPayload.Repository.Homepage,
		RepositoryID:   strconv.Itoa(event.WebhookPayload.Repository.ID),
		Branch:         strings.Replace(event.WebhookPayload.Ref, "refs/heads/", "", 1),
		CommitID:       event.WebhookPayload.HeadCommit.ID,
		ScanID:         event.HookId,
		ScanType:       "Webhook",
		SenderID:       event.WebhookPayload.Sender.ID,
		SenderType:     event.WebhookPayload.Sender.Type,
	}
	jsonData, err := json.Marshal(body)
	if err != nil {
		return err
	}
	log.Infof("scaner request body: %s", jsonData)
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
	if err != nil {
		return err
	}
	token, err := utils.GetTokenManager().GetToken()
	if err != nil {
		return err
	}
	req.Header.Set("username", "atomgit")
	req.Header.Set("token", token)
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	log.Infof("scaner response: %+v", resp)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	var scanTask ScanTask
	err = json.NewDecoder(resp.Body).Decode(&scanTask)
	if err != nil {
		return err
	}
	log.Infof("scanTask: %+v", scanTask)
	scanTaskJSON, err := json.Marshal(scanTask)
	if err != nil {
		return err
	}
	ent.ResponsePayload = string(scanTaskJSON)
	return nil
}

type ScanTask struct {
	Code     int      `json:"code"`
	Log      string   `json:"log"`
	TaskId   string   `json:"task_id"`
	TaskInfo TaskInfo `json:"task_info"`
}

type TaskInfo struct {
	TaskId       string `json:"task_id"`
	Organization string `json:"organizations"`
	Repo         string `json:"repo"`
	Branch       string `json:"branch"`
	TaskStatus   string `json:"task_status"`
	CreateTime   string `json:"create_time"`
	FinishTime   string `json:"finish_time"`
}

func (s EventService) StartRetryLoop() {
	go func() {
		ticker := time.NewTicker(1 * time.Minute)
		defer ticker.Stop()

		for {
			select {
			case <-ticker.C:
				err := s.retryEvents(context.Background())
				if err != nil {
					log.Errorf("Failed to retry events: %v", err)
				}
			}
		}
	}()
}

func (s EventService) retryEvents(ctx context.Context) error {
	tx := s.store.DB()
	events, err := s.store.Event(tx).ListRetryEvents(ctx, entity.EventListOptions{
		EventStatus: enums.EVENT_PROCESSING,
		RetryCount:  maxRetryCount,
	})
	if err != nil {
		return err
	}
	for _, event := range events {
		err := s.retryEvent(ctx, event)
		if err != nil {
			log.Errorf("Failed to retry event %s: %v", event.InstanceID, err)
		}
	}

	return nil
}

func (s EventService) retryEvent(ctx context.Context, ent *entity.Event) error {
	log.Infof("Retry event: %s, event id: %s", ent.InstanceID, ent.EventId)
	event := do.WebhookEvent{}
	err := json.Unmarshal([]byte(ent.RequestPayload), &event)
	err = requestScanServer(event, ent)
	if err != nil {
		ent.RetryCount++
		if ent.RetryCount >= maxRetryCount {
			ent.EventStatus = enums.EVENT_FAILED
		} else {
			ent.EventStatus = enums.EVENT_PROCESSING
		}
	} else {
		ent.EventStatus = enums.EVENT_SUCCESS
	}
	tx := s.store.DB()
	err = s.store.Event(tx).Update(ctx, ent)
	if err != nil {
		return err
	}
	return nil
}
